/**
 * <pre>
 * @(#)ScrollView.java 1.00 2009-11-19
 * 
 * Copyright (c) 1998, 2009 Regular Individual. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 * Contributor(s):
 *     Haixu Huang
 * </pre>
 **/
package cn.sh.huang.chart.view.container;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseWheelEvent;

import cn.sh.huang.chart.config.ScrollConfig;
import cn.sh.huang.chart.ext.enumtype.Position;
import cn.sh.huang.chart.ext.enumtype.ScrollBarType;
import cn.sh.huang.chart.ext.enumtype.ScrollType;
import cn.sh.huang.chart.ext.ui.ScrollBarUI;
import cn.sh.huang.chart.ext.ui.ScrollBarView;
import cn.sh.huang.chart.ext.ui.plain.ScrollBarUIImpl;
import cn.sh.huang.chart.view.AbstractView;

/**
 * <pre>
 * ScrollView ...
 * 
 * @version 1.0
 * </pre>
 */
public class ScrollView extends ViewContainer
{
    private final Grid[][] grids = { { new Grid(), new Grid(), new Grid() }, // 00, 10, 20
                                    { new Grid(), new Grid(), new Grid() }, // 01, 11, 21
                                    { new Grid(), new Grid(), new Grid() } }; // 02, 12, 22
    private final ScrollConfig config = new ScrollConfig(this);
    private boolean vbar = false, hbar = false;
    private final ScrollBarUI ui;
    private final Rectangle vbarRect = new Rectangle(), hbarRect = new Rectangle();
    private final ScrollBarView vbarView, hbarView;

    public ScrollView()
    {
        ui = new ScrollBarUIImpl(this);
        vbarView = ui.getVBarView();
        vbarView.setViewContainer(this);
        hbarView = ui.getHBarView();
        hbarView.setViewContainer(this);
    }

    public void mouseWheelMoved(MouseWheelEvent e)
    {
        AbstractView view = getElement(Position.CORE);
        if (view != null) {
            Dimension viewSize = view.getViewSize();
            Dimension gridSize = this.getGridRect(view).getSize();
            Point gridAnchor = view.getGridAnchor();
            int step = e.getWheelRotation() * ScrollBarUI.SCROLL_UNIT;
            if (vbar && !e.isShiftDown()) {
                ui.onScroll(ScrollBarType.VERTICAL, viewSize.height, gridSize.height, gridAnchor.y, step);
                ui.offScroll(ScrollBarType.VERTICAL);
                return;
            } else if (hbar && e.isShiftDown()) {
                ui.onScroll(ScrollBarType.HORIZONTAL, viewSize.width, gridSize.width, gridAnchor.x, step);
                ui.offScroll(ScrollBarType.HORIZONTAL);
                return;
            }
        }
        super.mouseWheelMoved(e);
    }

    public void setElement(Position p, AbstractView view)
    {
        int[] pi = p.code(); // position's code
        AbstractView oldView = grids[pi[0]][pi[1]].view; // col:row
        if (oldView != null) {
            oldView.dispose();
        }
        view.setViewContainer(this);
        grids[pi[0]][pi[1]].view = view;
    }

    public AbstractView getElement(Position p)
    {
        int[] pi = p.code(); // position's code
        return grids[pi[0]][pi[1]].view; // col:row
    }

    public void setVerticalScrollType(ScrollType vertType)
    {
        config.setVerticalScrollType(vertType);
    }

    public void setHorizontalScrollType(ScrollType horzType)
    {
        config.setHorizontalScrollType(horzType);
    }

    public void setScrollPos(ScrollBarType barType, int pos)
    {
        if (barType.equals(ScrollBarType.VERTICAL)) {
            vbarView.setPosition(pos);
            setVerticalScrollPos(getElement(Position.LEFT_CENTER), pos);
            setVerticalScrollPos(getElement(Position.CORE), pos);
            setVerticalScrollPos(getElement(Position.RIGHT_CENTER), pos);
        } else {
            hbarView.setPosition(pos);
            setHorizontalScrollPos(getElement(Position.CENTER_TOP), pos);
            setHorizontalScrollPos(getElement(Position.CORE), pos);
            setHorizontalScrollPos(getElement(Position.CENTER_BOTTOM), pos);
        }
        repaint();
    }

    private void setVerticalScrollPos(AbstractView view, int pos)
    {
        if (view == null) {
            return;
        }
        view.setGridAnchor(view.getGridAnchor().x, pos);
    }

    private void setHorizontalScrollPos(AbstractView view, int pos)
    {
        if (view == null) {
            return;
        }
        view.setGridAnchor(pos, view.getGridAnchor().y);
    }

    private int[][] getScrollViewSize(boolean isPreferred)
    {
        boolean autoVert = config.getVerticalScrollType().auto(); // auto show the scroll bar?
        boolean autoHorz = config.getHorizontalScrollType().auto();
        int[] widths = new int[3], heights = new int[3];
        for (int col = 0; col < 3; col++) {
            for (int row = 0; row < 3; row++) {
                AbstractView view = grids[col][row].view;
                if (view != null) {
                    Dimension preferred = view.getPreferredSize();
                    Dimension minimum = view.getMinimumSize();
                    Dimension size = null;
                    if (isPreferred) {
                        size = preferred;
                    } else {
                        size = minimum;
                        if (autoVert) {
                            size.height = MINIMUM_VIEW_WIDTH;
                        }
                        if (autoHorz) {
                            size.width = MINIMUM_VIEW_WIDTH;
                        }
                    }
                    widths[col] = Math.max(widths[col], size.width);
                    heights[row] = Math.max(heights[row], size.height);
                }
            }
        }
        return new int[][] { widths, heights };
    }

    /*
     * (non-Javadoc)
     * @see cn.sh.huang.chart.view.AbstractView#getMinimumSize()
     */
    public Dimension getMinimumSize()
    {
        int[][] gridSizes = getScrollViewSize(false);
        int[] widths = gridSizes[0], heights = gridSizes[1];
        int width = widths[0] + widths[1] + widths[2] + ui.getBarUnit(), height =
            heights[0] + heights[1] + heights[2] + ui.getBarUnit();
        return new Dimension(Math.max(width, MINIMUM_VIEW_WIDTH), Math.max(height, MINIMUM_VIEW_WIDTH));

    }

    /*
     * (non-Javadoc)
     * @see cn.sh.huang.chart.view.AbstractView#getPreferredSize()
     */
    public Dimension getPreferredSize()
    {
        int[][] gridSizes = getScrollViewSize(true);
        int[] widths = gridSizes[0], heights = gridSizes[1];
        int width = widths[0] + widths[1] + widths[2], height = heights[0] + heights[1] + heights[2];
        return new Dimension(Math.max(width, MINIMUM_VIEW_WIDTH), Math.max(height, MINIMUM_VIEW_WIDTH));
    }

    public void setViewSize(final int width, final int height)
    {
        int[][] gridSizes = getScrollViewSize(true); // get preferred size
        int[] widths = gridSizes[0], heights = gridSizes[1];
        int centerWidth = width - (widths[0] + widths[2]); // center area's size
        int centerHeight = height - (heights[0] + heights[2]);
        boolean autoVert = config.getVerticalScrollType().auto(); // auto show the scroll bar?
        boolean autoHorz = config.getHorizontalScrollType().auto();
        vbar = false;
        hbar = false;
        if (autoHorz && centerWidth < widths[1]) { // need horizontal scroll bar, vertical distance reduce a scroll bar
                                                   // thickness
            centerHeight -= ui.getBarUnit();
            hbar = true;
        }
        if (autoVert && centerHeight < heights[1]) { // need vertical scroll bar, horizontal distance reduce a scroll
                                                     // bar thickness
            centerWidth -= ui.getBarUnit();
            vbar = true;
            if (!hbar && autoHorz && centerWidth < widths[1]) { // horizontal distance decreased, re-calculate
                centerHeight -= ui.getBarUnit();
                hbar = true;
            }
        }
        // if center area's width < view area's, or no horizontal bar, set the width to center area's width
        if (widths[1] < centerWidth || !autoHorz) {
            widths[1] = centerWidth;
        }
        if (heights[1] < centerHeight || !autoVert) {
            heights[1] = centerHeight;
        }
        for (int col = 0, x = 0; col < 3; col++) {
            int w = col == 1 ? centerWidth : widths[col];
            for (int row = 0, y = 0; row < 3; row++) {
                int h = row == 1 ? centerHeight : heights[row];
                Grid grid = grids[col][row];
                grid.rect.setRect(x, y, w, h);
                if (grid.view != null) {
                    grid.view.setViewSize(widths[col], heights[row]);
                    Point gridAnchor = grid.view.getGridAnchor();
                    if (gridAnchor.x + w > widths[col]) { // when stretch the window, keep the original viewRect anchor
                        gridAnchor.x = widths[col] - w;
                    }
                    if (gridAnchor.y + h > heights[row]) {
                        gridAnchor.y = heights[row] - h;
                    }
                    grid.view.setGridAnchor(gridAnchor.x, gridAnchor.y);
                }
                y += h;
            }
            x += w;
        }
        if (vbar) {
            int x = widths[0] + centerWidth + widths[2], y = heights[0];
            int w = ui.getBarUnit(), h = centerHeight;
            vbarRect.setRect(x, y, ui.getBarUnit(), h);
            vbarView.setViewSize(w, h);
            vbarView.setLength(heights[1], centerHeight);
        } else {
            vbarRect.setRect(0, 0, 0, 0);
            vbarView.setViewSize(0, 0);
        }
        if (hbar) {
            int x = widths[0], y = heights[0] + centerHeight + heights[2];
            int w = centerWidth, h = ui.getBarUnit();
            hbarRect.setRect(x, y, w, h);
            hbarView.setViewSize(w, h);
            hbarView.setLength(widths[1], centerWidth);
        } else {
            hbarRect.setRect(0, 0, 0, 0);
            hbarView.setViewSize(0, 0);
        }
        super.setViewSize(width, height);
    }

    /*
     * (non-Javadoc)
     * @see cn.sh.huang.chart.view.AbstractView#render(java.awt.Graphics2D)
     */
    public void render(Graphics2D g2d)
    {
        for (int col = 0; col < 3; col++) {
            for (int row = 0; row < 3; row++) {
                if (grids[col][row].view != null) {
                    grids[col][row].view.render(g2d);
                }
            }
        }
        if (vbar) { // rendering vertical bar
            vbarView.render(g2d);
        }
        if (hbar) { // rendering horizontal bar
            hbarView.render(g2d);
        }
    }

    /**
     * @return the config
     */
    public ScrollConfig getConfig()
    {
        return config;
    }

    /*
     * (non-Javadoc)
     * @see cn.sh.huang.chart.view.container.Container#getGridRect(cn.sh.huang.chart.view.AbstractView)
     */
    public Rectangle getGridRect(AbstractView view)
    {
        for (int col = 0; col < 3; col++) {
            for (int row = 0; row < 3; row++) {
                if (view.equals(grids[col][row].view)) {
                    return (Rectangle) grids[col][row].rect.clone();
                }
            }
        }
        return view.equals(vbarView) ? (Rectangle) vbarRect.clone() // if it's vbar
            : view.equals(hbarView) ? (Rectangle) hbarRect.clone() // if it's hbar
                : null;
    }
}

class Grid
{
    AbstractView view = null;
    Rectangle rect = new Rectangle();
}
